﻿using System;
using System.ComponentModel;
using System.Windows.Input;
using RG.Common;
using RG.Common.Misc;
using log4net;
using Microsoft.Practices.Unity;

namespace RG.Common.WPF
{
    public interface IWorker
    {
        bool IsFree { get; set; }

        void Do<TResult>(Func<TResult> DoWork, Action<TResult> OnCompleted, Action<Exception> OnError);

        event WorkerStateChangedArgs StateChanged;
    }

    public delegate void WorkerStateChangedArgs(object sender, bool isFree);

    [SupportDataBinding]
    public class AsyncWorker : IWorker
    {
        public AsyncWorker()
        {
            IsFree = true;
            if (StateChanged != null) StateChanged(this, IsFree);
        }

        public bool IsFree { get; set; }

        public void Do<TResult>(Func<TResult> DoWork, Action<TResult> OnCompleted, Action<Exception> OnError)
        {
            if (!IsFree)
                throw new ApplicationException("Однако занято");

            IsFree = false;
            if(StateChanged != null) StateChanged(this, IsFree);

            var bw = new BackgroundWorker();
            bw.DoWork += (sender, e) => e.Result = DoWork();

            bw.RunWorkerCompleted += (sender, e) =>
                                         {
                                             try
                                             {
                                                 if (e.Error == null)
                                                     try
                                                     {
                                                         OnCompleted((TResult)e.Result);
                                                     }
                                                     catch (Exception err)
                                                     {
                                                         OnError(err);
                                                     }
                                                 else
                                                     OnError(e.Error);
                                             }
                                             finally
                                             {
                                                 IsFree = true;
                                                 if (StateChanged != null) StateChanged(this, IsFree);
                                             }
                                             CommandManager.InvalidateRequerySuggested();
                                         };

            bw.RunWorkerAsync();
        }

        public event WorkerStateChangedArgs StateChanged;
    }

    [SupportDataBinding]
    public class SyncWorker : IWorker
    {
        public SyncWorker()
        {
            IsFree = true;
            if (StateChanged != null) StateChanged(this, IsFree);
        }

        public bool IsFree { get; set; }

        public void Do<TResult>(Func<TResult> DoWork, Action<TResult> OnCompleted, Action<Exception> OnError)
        {
            IsFree = false;
            if (StateChanged != null) StateChanged(this, IsFree);
            try
            {
                var r = DoWork();
                OnCompleted(r);
            }
            catch (Exception e)
            {
                OnError(e);
            }
            IsFree = true;
            if (StateChanged != null) StateChanged(this, IsFree);
        }

        public event WorkerStateChangedArgs StateChanged;
    }

    public static class IWorkerExtension
    {
        public static void Do(this IWorker worker, Action doWork, Action<Exception> onError)
        {
            worker.Do(
                () => { doWork(); return true; },
                fake => { },             
                onError);
        }

        public static void Do(this IWorker worker, Action doWork, Action onOk, Action<Exception> onError)
        {
            worker.Do(
                () => { doWork(); return true; },
                fake => onOk(),
                onError);
        }

    }

    public class WorkplaceWorker: INotifyPropertyChanged, IFirePropertyChanged
    {
        [Dependency]
        public IWorkplace Workplace { get; set; }

        private IWorker worker;

        private static readonly ILog log = LogManager.GetLogger(typeof(WorkplaceWorker));

        [Dependency]
        public IWorker Worker
        {
            get { return worker; }
            set
            {
                worker = value;
// ReSharper disable SuspiciousTypeConversion.Global
                if (worker is INotifyPropertyChanged)
                    (worker as INotifyPropertyChanged).PropertyChanged += (sender, args) => OnPropertyChanged("IsFree");
                // ReSharper restore SuspiciousTypeConversion.Global
            }
        }

        public bool IsFree { get { return Worker.IsFree; } } 

        public void OnError(Exception err)
        {
            log.TraceError(err, "Worker exception");
            Workplace.Error(err.Message);
        }

        public void Do<TResult>(Func<TResult> DoWork, Action<TResult> OnCompleted)
        {
            Worker.Do(DoWork, OnCompleted, OnError);
        }


        public void Do(Action doWork, Action onOk)
        {
            Worker.Do(
                () => { doWork(); return true; },
                fake => onOk(),
                OnError);
        }


        public void Do(Action doWork)
        {
            Worker.Do(
                () => { doWork(); return true; },
                fake => { },
                OnError);
        }


        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}